/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package coalescent.data;

import coalescent.model.PopGenModel;

import commons.core.MutableData;
import commons.util.ExceptionUtil;

import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Mutable version of the {@code PopGenData}.
 *
 * @param <A> allele type
 * @param <M> the data model (subtype of {@code PopGenModel})
 * @param <D> the data (subtype of PopGenData) of which this is a mutable version
 * @author Susanta Tewari
 */
public abstract class MutablePopGenData<A, M extends PopGenModel<?>,
        D extends PopGenData<A, ?, M, D>> extends MutableData<M, D> {

    /** Field description */
    private Map<A, Integer> alleleFreq;

    /**
     * A data and its mutable version must reside in the same package. Mutable data should always
     * be created from the data and nowhere else.
     *
     * @param model model
     * @param alleleFreq allele frequency
     * @throws NullPointerException if any of the parameter is {@code null}
     * @throws IllegalArgumentException if {@code alleleFreq} is empty
     */
    MutablePopGenData(M model, Map<A, Integer> alleleFreq) {

        super(model);

        if (alleleFreq == null) ExceptionUtil.throwIllegalArgExNull("alleleFreq");
        if (alleleFreq.isEmpty())
            ExceptionUtil.throwIllegalArgEx(getClass(), "Map_Is_Empty", "alleleFreq");

        this.alleleFreq = new HashMap<>(alleleFreq);
    }

    /**
     * Method description
     *
     *
     * @return
     */
    protected final Map<A, Integer> getAlleleFreq() {
        return alleleFreq;
    }

    /**
     * Sets the old frequency to the specified one for the given allele.
     *
     * @param allele an allele in this data set
     * @param frequency the new frequency of the given allele
     * @throws NullPointerException if {@code allele} is {@code null}
     * @throws IllegalArgumentException if, <ol> <li>{@code frequency} &le 0</li></ol>
     */
    public final void setAlleleFrequency(A allele, int frequency) {


        // allele present ?
        checkAllelePresent(allele);


        // frequency <= 0 ?
        if (frequency <= 0)
            ExceptionUtil.throwIllegalArgEx(getClass(), "Invalid_Freq_MutablePopGenData",
                frequency);


        // get old freq
        int oldFreq = alleleFreq.get(allele);

        alleleFreq.put(allele, frequency);
    }

    /**
     * Sets the specified old allele to the specified new one.
     *
     * @param oldAllele an allele in this data set to be replaced by the specified new allele
     * @param newAllele a new allele replacing the specified old allele
     */
    public final void setAllele(A oldAllele, A newAllele) {

        if (newAllele == null) {
            ExceptionUtil.throwIllegalArgExNull("newAllele");
        }


        // allele present ?
        checkAllelePresent(oldAllele);


        // old & new same allele ?
        if (oldAllele.equals(newAllele)) {

            Logger.getLogger(getClass().getName()).log(Level.FINE,
                             "new allele is same as the old allele: " + oldAllele);

            return;
        }


        // get old freq
        Integer oldFreq = alleleFreq.get(oldAllele);


        // set new allele with old freq
        alleleFreq.put(newAllele, oldFreq);
    }

    /**
     * Throws {@code IllegalArgumentException} if the parameter {@code allele} is not present in
     * this data set.
     *
     * @param allele allele supposedly present in this data set
     * @throws NullPointerException if parameter is {@code null}
     * @throws IllegalArgumentException if {@code allele} is not present
     */
    protected final void checkAllelePresent(A allele) {

        if (allele == null) ExceptionUtil.throwIllegalArgExNull("allele");

        if (!alleleFreq.containsKey(allele)) {
            ExceptionUtil.throwIllegalArgEx(getClass(), "Allele_Not_Present", allele);
        }
    }

    /**
     * Removes an allele from this data set.
     *
     * @param allele an allele in this data set that is to be removed
     * @throws NullPointerException if {@code allele} is {@code null}
     */
    public final void removeAllele(A allele) {
        alleleFreq.remove(allele);
    }
}
